#!/usr/local/bin/perl
# Yen-Ming Lee <leeym@leeym.com>
# $Id$
use strict;
use Cwd 'abs_path';
use FindBin '$Script';
use Data::Dumper;

my @l;
my @L;
my %NM;
my %PATH;
my %EXT;
my @RPATHLINK;
my @RPATH;
my %SYMBOL;
my $o;
my $verbose = defined($ENV{'DEVTIDY_VERBOSE'});
my $debug   = defined($ENV{'DEVTIDY_DEBUG'});

my $SYSLIB = "c,m,z,gcc,gcc_s,glib-2.0,stdc++,bsd-compat";
$SYSLIB = $ENV{'DEVTIDY_SYSLIB'} if defined($ENV{'DEVTIDY_SYSLIB'});
my %SYSLIB;
map { $SYSLIB{$_} = 1; } split (',', $SYSLIB);
print "SYSLIB:\n" . Dumper(\%SYSLIB) if $debug;

for (my $i = 0; $i < $#ARGV; $i++)
{
  my $argv = $ARGV[$i];
  my $next = $ARGV[$i + 1];
  my $arg  = substr($argv, 2);
  push (@l, $arg) if ($argv =~ /^-l/);
  push (@L, abs_path($arg)) if ($argv =~ /^-L/ && -d $arg);
  push (@RPATHLINK, abs_path($next)) if $argv eq '-rpath-link';
  push (@RPATH,     abs_path($next)) if $argv eq '-rpath';
  $o = $next if $argv eq '-o';
  $o = $next if $argv eq '-soname';
}
exec("/usr/bin/ld @ARGV") if !$o;
@L = (@RPATHLINK, @RPATH, @L);

foreach my $l (@l)
{
  foreach my $L (@L)
  {
    foreach my $ext (qw(a so))
    {
      if (-f "$L/lib$l.$ext")
      {
        $EXT{$l}  = $ext;
        $PATH{$l} = "$L/lib$l.$ext";
      }
    }
  }
}
print "EXT:\n" . Dumper(\%EXT)   if $debug;
print "PATH:\n" . Dumper(\%PATH) if $debug;

map { nm($PATH{$_}) } (keys %EXT);

my %order;
my $n   = 0;
my $_a  = 0;
my $_so = 0;
my $cur = 0;

foreach my $lib (@l)
{
  next if $SYSLIB{$lib};
  $_a = $lib if $EXT{$lib} eq 'a';
  $_so = $lib if !$_so && $EXT{$lib} eq 'so';
  $order{$lib} = ++$cur;
}

my $macro = 'PHP_ADD_LIBRARY_WITH_PATH';
my $w;
if ($order{$_a} > $order{$_so})
{
  $w .= "\t-l$_a is an archive (.a), and -l$_so is a shared object (.so)\n";
  if (-f "config.m4")
  {
    $w .= "\t$macro($_so...) should be in front of $macro($_a...)\n";
  }
  else
  {
    $w .= "\t-l$_a should be in front of -l$_so\n";
  }
  $w .= "\n";
}

foreach my $lib1 (keys %PATH)
{
  next if !defined($NM{$lib1});
  next if !defined($NM{$lib1}{U});
  foreach my $symbol (@{ $NM{$lib1}{U} })
  {
    my $lib2 = $SYMBOL{$symbol};
    next if !$lib2;
    next if $SYSLIB{$lib1};
    next if $SYSLIB{$lib2};
    next if $lib1 eq $lib2;
    next if $EXT{$lib1} eq 'so' and $EXT{$lib2} eq 'so';
    if ($order{$lib1} > $order{$lib2})
    {
      $w .= "\t-l$lib1 requires -l$lib2 for $symbol\n";
      if (-f "config.m4")
      {
        $w .= "\t$macro($lib2...) should be in front of $macro($lib1...)\n";
      }
      else
      {
        $w .= "\t-l$lib1 should be in front of -l$lib2\n";
      }
      $w .= "\n";
    }
  }
}

my $ret = system("/usr/bin/ld @ARGV");
exit($ret) if $ret;

# check unused lib
nm($o);
print Dumper(\%NM) if $debug;
my %DEP;
dep($o, %DEP);
print "DEP:\n" . Dumper(\%DEP) if $debug;
foreach my $lib (@l)
{
  next if $SYSLIB{$lib};
  next if $DEP{$lib};
  if (defined($NM{$lib}))
  {
    $w .= "\t$o does not require -l$lib\n";
  }
  else
  {
    warn "\nWARNING:\n\tno symbol table in $lib\n\n";
  }
}
if ($w)
{
  $w = "\nWARNING:\n$w\n";
  ($ENV{'DEVTIDY_FORCE'}) ? warn $w : die $w;
}

## END ----------------------------------------------------------

sub nm
{
  my $file = shift;
  print "nm($file)\n" if $verbose;

=leeym
  if (-l $file)
  {
    my $real = readlink($file);
    $file =~ s,/[^/]+$,/,;
    $file .= $real;
    $file = $real if ($real =~ m{^/});
  }
=cut

  my $lib = libname($file);
  return if defined($NM{$lib});
  return if defined($SYSLIB{$lib});
  foreach my $line (`/usr/bin/nm $file 2> /dev/null`)
  {
    chomp($line);
    my ($addr, $type, $symbol) = ($1, $2, $3) if $line =~ /^(.{8}) (.) (.+)$/;
    next if !$symbol;
    next if $symbol eq '__eprintf';
    next if lc($type) eq $type;
    $symbol =~ s{\@\@.*$}{};
    $SYMBOL{$symbol} = $lib if ($type ne 'U' && $type ne 'A');
    push (@{ $NM{$lib}{$type} }, $symbol);
  }
}

sub libname
{
  my $lib = shift;
  print "libname($lib)\n" if $verbose;
  $lib =~ s{^.*/}{};
  $lib =~ s{^lib}{};
  $lib =~ s{(\.\d+)+$}{};
  $lib =~ s{\.(a|so)$}{};
  return $lib;
}

sub dep
{
  my $file = shift;
  my %_DEP;
  print "dep($file)\n" if $verbose;
  my $lib = libname($file);
  foreach my $lib2 (@l)
  {
    next if $SYSLIB{$lib2};
    next if $lib eq $lib2;
    next if $DEP{$lib2};
    my $key;
    $key = 'U' if $EXT{$lib2} eq 'so';
    $key = 'T' if $EXT{$lib2} eq 'a';
    my $found;
    print "Check if $lib requires $lib2 ... " if $verbose;

    foreach my $tt (qw(T V W))
    {
      next if !exists($NM{$lib2});
      next if !exists($NM{$lib2}{$tt});
      foreach my $symbol2 (@{ $NM{$lib2}{$tt} })
      {
        next if !exists($NM{$lib});
        next if !exists($NM{$lib}{$key});
        foreach my $symbol (@{ $NM{$lib}{$key} })
        {
          next if $symbol ne $symbol2;
          $found = $symbol;
          $DEP{$lib2} = $_DEP{$lib2} = $symbol;
          print "Yes, [$tt] $symbol\n" if $verbose;
          last;
        }
        last if $found;
      }
      last if $found;
    }
    print "No\n" if !$found && $verbose;
  }
  print Dumper(\%_DEP) if $debug;
  return %DEP if !scalar(%_DEP);
  foreach my $lib2 (keys %_DEP)
  {
    next if $lib eq $lib2;
    my %_DEP2 = dep($lib2);
    foreach my $lib3 (keys %_DEP2)
    {
      $DEP{$lib3} = $_DEP2{$lib3};
    }
  }
  return %DEP;
}
